package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.course.CatalogueClient;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CataSimpleInfoDTO;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.course.CourseSimpleInfoDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.domain.query.PageQuery;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.ObjectUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.LearningPlanDTO;
import com.tianji.learning.domain.vo.LearningLessonVO;
import com.tianji.learning.domain.vo.LearningPlanPageVO;
import com.tianji.learning.domain.vo.LearningPlanVO;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.PlanStatus;
import com.tianji.learning.mapper.LearningLessonMapper;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.model.po.LearningLesson;
import com.tianji.learning.model.po.LearningRecord;
import com.tianji.learning.service.ILearningLessonService;
import com.tianji.learning.service.ILearningRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 学生课表，服务实现类
 */
@Service
@Slf4j
@SuppressWarnings("ALL")
@RequiredArgsConstructor
public class LearningLessonServiceImpl extends ServiceImpl<LearningLessonMapper, LearningLesson> implements ILearningLessonService {

    private final CourseClient courseClient;

    private final CatalogueClient catalogueClient;

    @Autowired
    @Lazy
    private ILearningRecordService learningRecordService;


    /**
     * 添加课程信息到用户课程表
     *
     * @param userId
     * @param courseIds
     */
    @Override
    @Transactional
    public void addUserLessons(Long userId, List<Long> courseIds) {

        //查询课程有效期
        List<CourseSimpleInfoDTO> simpleInfoList = courseClient.getSimpleInfoList(courseIds);
        if (CollUtils.isEmpty(simpleInfoList)) {
            log.error("课程信息不存在, 无法添加到课程表");
            return;
        }

        //循环遍历, 处理LearningLesson数据
        List<LearningLesson> list = new ArrayList<>(simpleInfoList.size());
        for (CourseSimpleInfoDTO coursesimpleInfo : simpleInfoList) {
            LearningLesson lesson = new LearningLesson();

            //获取过期时间
            Integer validDuration = coursesimpleInfo.getValidDuration();
            if (validDuration != null && validDuration > 0) {
                LocalDateTime now = LocalDateTime.now();
                lesson.setCreateTime(now);
                lesson.setExpireTime(now.plusMonths(validDuration));
            }

            //Lesson字段填充
            lesson.setUserId(userId);
            lesson.setCourseId(coursesimpleInfo.getId());
            list.add(lesson);
        }

        //批量新增
        this.saveBatch(list);
    }

    @Override
    public PageDTO<LearningLessonVO> pageQuery(PageQuery query) {

        //当前用户的id
        Long userId = UserContext.getUser();

        //1. 编写sql语句
        //select * form learning_lesson where userId = ? order by ? limit
        Page<LearningLesson> page = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .page(query.toMpPage("latest_learn_time", false));

        //2.判断结果
        List<LearningLesson> lessonList = page.getRecords();
        if (CollUtils.isEmpty(lessonList)) {
            //说明用户没有购买任何课程
            return PageDTO.empty(page);
        }

        //3. 补充基础数据
        //获取当前用户的所有课程id
        Set<Long> courseIdList = lessonList.stream().map(LearningLesson::getCourseId).collect(Collectors.toSet());
        //远程调用获取这些课程的详细信息
        List<CourseSimpleInfoDTO> courseClientSimpleInfoList = courseClient.getSimpleInfoList(courseIdList);
        //将courseClientSimpleInfoList装换位map集合,方便取出数据(要保证,key不重复)
        Map<Long, CourseSimpleInfoDTO> courdeMap = courseClientSimpleInfoList.stream()
                .collect(Collectors.toMap(CourseSimpleInfoDTO::getId, course -> course));

        //4. 转换数据类型
        List<LearningLessonVO> learningLessonVOList = new ArrayList<>();
        for (LearningLesson lesson : lessonList) {
            LearningLessonVO lessonVO = BeanUtils.toBean(lesson, LearningLessonVO.class);

            //第一个参数是:要检索的键。,第二个参数: 如果映射中不存在该键，则返回此默认值。(可以避免空指针异常)
            CourseSimpleInfoDTO course = courdeMap.getOrDefault(lesson.getCourseId(), new CourseSimpleInfoDTO());
            lessonVO.setCourseName(course.getName());
            lessonVO.setCourseCoverUrl(course.getCoverUrl());
            lessonVO.setSections(course.getSectionNum());

            learningLessonVOList.add(lessonVO);
        }

        //5. 返回结果
        return PageDTO.of(page, learningLessonVOList);
    }

    /**
     查询正在学习的课程
     *
     */
    @Override
    public LearningLessonVO getNowLearningLesson() {
        //当前用户ID
        Long userId = UserContext.getUser();

        //1. 获取当前用户最近一次课程状态是学习中的的课程
        LearningLesson learningLesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getStatus, LessonStatus.LEARNING)
                .orderByDesc(LearningLesson::getLatestLearnTime)
                .last("limit 1")
                .one();
//                .list().get(0);
        if (ObjectUtils.isEmpty(learningLesson)) {
            //没有正在学习的课程
            return null;
        }

        //2. 拷贝PO基础属性到VO
        LearningLessonVO learningLessonVO = BeanUtils.copyBean(learningLesson, LearningLessonVO.class);

        //3. 根据最近一次学习的课程id获取课程信息
        CourseFullInfoDTO course = courseClient.getCourseInfoById(learningLesson.getCourseId(), false, false);
        if (ObjectUtils.isEmpty(course)) {
            throw new BadRequestException("课程不存在");
        }

        //3.1 向learningLessonVO中添加需要补充的数据
        learningLessonVO.setCourseName(course.getName());
        learningLessonVO.setCourseCoverUrl(course.getCoverUrl());
        learningLessonVO.setSections(course.getSectionNum());

        //4. 统计课程表中的课程总数量
        Integer courseAmount = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .count();
        learningLessonVO.setCourseAmount(courseAmount);

        //5. 根据最近一次学习的小节id获取学习小节的名称,学习小节序号
        List<CataSimpleInfoDTO> cataSimpleInfoDTOList =
                catalogueClient.batchQueryCatalogue(CollUtils.newArrayList(learningLesson.getLatestSectionId()));

        if (CollUtils.isNotEmpty(cataSimpleInfoDTOList)) {
            CataSimpleInfoDTO cataSimpleInfoDTO = cataSimpleInfoDTOList.get(0);
            learningLessonVO.setLatestSectionName(cataSimpleInfoDTO.getName());
            learningLessonVO.setLatestSectionIndex(cataSimpleInfoDTO.getCIndex());
        }

        return learningLessonVO;
    }

    /**
     * 删除已失效课程
     * @param userId
     * @param courseId
     */
    @Override
    public void deleteLesson(Long userId, Long courseId) {
        //1. 判断课程是否存在
        LearningLesson learningLesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .last("limit 1")
                .one();
        if (ObjectUtils.isNull(learningLesson)) {
            throw new BadRequestException("课程不存在");
        }

        //2. 判断课程是否已经失效
        Boolean expireResult = LocalDateTime.now().isBefore(learningLesson.getExpireTime());
        if (expireResult) {
            throw new BadRequestException("课程未失效,不能删除");
        }

        //3. 删除课程
        boolean result = this.removeById(learningLesson.getId());
        if (!result) {
            throw new DbException("课程删除失败");
        }
    }

    /**
     * 检查课程是否有效
     *
     * @param courseId
     * @return
     */
    @Override
    public Long isLessonValid(Long courseId) {
        //当前用户ID
        Long userId = UserContext.getUser();

        //1. 根据课程id判断当前用户是否用有该课程
        LearningLesson learningLesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .last("limit 1")
                .one();
        if (ObjectUtils.isNull(learningLesson) || LocalDateTime.now().isAfter(learningLesson.getExpireTime())) {
            return null;
        }

        //2. 视频未过期
        return learningLesson.getId();
    }

    /**
     * 查询用户课表中指定课程状态
     *
     * @param courseId
     * @return
     */
    @Override
    public LearningLessonVO getLessonStatusByCourseId(Long courseId) {
        //当前用户ID
        Long userId = UserContext.getUser();

        //1. 判断当前用户是否有该课程
        LearningLesson learningLesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .last("limit 1")
                .one();
        if (ObjectUtils.isNull(learningLesson)) {
            log.info("用户未购买当前课程");
            return null;
        }

        //封装LearningLessonVO对象返回个前端
        LearningLessonVO learningLessonVO = BeanUtils.copyBean(learningLesson, LearningLessonVO.class);

        return learningLessonVO;
    }


    /**
     * 统计课程学习人数
     *
     * @param courseId 课程id
     * @return 学习人数
     */
    @Override
    public Integer countLearningLessonByCourse(Long courseId) {
        //sql 查询语句: select count(*) from learning_lesson where course_id = ?
        Integer count = this.lambdaQuery()
                .eq(LearningLesson::getCourseId, courseId)
                .count();
        return count;
    }


    @Override
    public LearningLesson queryByUserAndCourseId(Long userId, Long courseId) {
        return getOne(buildUserIdAndCourseIdWrapper(userId, courseId));
    }

    private LambdaQueryWrapper<LearningLesson> buildUserIdAndCourseIdWrapper(Long userId, Long courseId) {

        LambdaQueryWrapper<LearningLesson> queryWrapper = new QueryWrapper<LearningLesson>()
                .lambda()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId);
        return queryWrapper;
    }

    /**
     * 创建学习计划
     *
     * @param dto
     */
    @Override
    public void addPlans(LearningPlanDTO dto) {
        //当前用户信息
        Long userId = UserContext.getUser();

        //1. 判断用户是否拥有该课程
        LearningLesson learningLesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, dto.getCourseId())
                .one();
        if (learningLesson == null) {
            throw new BadRequestException("用户没有购买该课程");
        }
        if (ObjectUtils.equal(learningLesson.getStatus(), LessonStatus.EXPIRED.getValue())) {
            throw new BadRequestException("课程已经过期");
        }

        //2. 修改数据库: update learning_lesson set week_freq = ? , plan_status = ? where  user_id = ? and course_id = ?
        boolean result = this.lambdaUpdate()
                .set(LearningLesson::getWeekFreq, dto.getFreq())
                .set(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING)
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, dto.getCourseId())
                .update();
        if (!result) {
            throw new DbException("学习计划创建失败");
        }
    }

    @Resource
    private LearningRecordMapper recordMapper;

    /**
     * 查询学习计划进度
     *
     * @param pageQuery
     * @return
     */
    @Override
    public LearningPlanPageVO getPlans(PageQuery pageQuery) {
        //用户ID
        Long userId = UserContext.getUser();

        //数据返回对象
        LearningPlanPageVO learningPlanPageVO = new LearningPlanPageVO();

        LocalDate startOfWeek = LocalDate.now().with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
        LocalDate endOfWeek = LocalDate.now().with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
        //本周已经学完小节数量
        Integer weekFinished = recordMapper.selectCount(new LambdaQueryWrapper<LearningRecord>()
                .eq(LearningRecord::getUserId, userId)
                .eq(LearningRecord::getFinished, true)
                .gt(LearningRecord::getFinishTime, startOfWeek)
                .lt(LearningRecord::getFinishTime, endOfWeek)
        );
        //本周计划学习小节数量
        List<LearningLesson> learningLessonList = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING)
                .in(LearningLesson::getStatus, LessonStatus.NOT_BEGIN, LessonStatus.LEARNING)
                .list();
        Integer weekTotalPlan = learningLessonList.stream().mapToInt(LearningLesson::getWeekFreq).sum();

        //1. 获取该用户所有学习计划状态是计划进行中的课程
        Page<LearningLesson> learningLessonPage = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING)
                .in(LearningLesson::getStatus, LessonStatus.NOT_BEGIN, LessonStatus.LEARNING)
                .page(pageQuery.toMpPage("latest_learn_time", false));
        if (CollUtils.isEmpty(learningLessonPage.getRecords())) {
            log.info("该用户没有计划中的课程");
            return learningPlanPageVO.emptyPage(learningLessonPage);
        }

        //2. 收集需要返回的数据
        //获取所有计划中的课程
        List<LearningLesson> lessonList = learningLessonPage.getRecords();

        //2.1 整理计划中的课程列表
        List<LearningPlanVO> learningPlanVOList = BeanUtils.copyList(lessonList, LearningPlanVO.class);

        //2.1.1 通过CourseClient,获取这些课程的详细信息
        //课程id集合
        Set<Long> courseIdList = learningPlanVOList.stream().map(LearningPlanVO::getCourseId).collect(Collectors.toSet());
        //feign调用获取课程详细信息集合
        List<CourseSimpleInfoDTO> courseList = courseClient.getSimpleInfoList(courseIdList);
        if (CollUtils.isEmpty(courseList)) {
            throw new BadRequestException("课程信息不存在！");
        }
        //将课程详细信息集合转换为map
        Map<Long, CourseSimpleInfoDTO> courseMap = courseList.stream().collect(Collectors.toMap(CourseSimpleInfoDTO::getId, r -> r));

        //2.1.2 在learning_record表中获取对应课表本周的学完的小节数
        Set<Long> lessonIdList = learningPlanVOList.stream().map(LearningPlanVO::getId).collect(Collectors.toSet());

        List<LearningRecord> learningRecordList = learningRecordService.lambdaQuery()
                .eq(LearningRecord::getFinished, true)
                .in(LearningRecord::getLessonId, lessonIdList)
                .between(LearningRecord::getFinishTime, LocalDateTime.of(startOfWeek, LocalTime.MIN), LocalDateTime.of(endOfWeek, LocalTime.MAX))
                .list();
        //将learningRecordList收集为Map<课表id,该课表本周已经学习数量>
        Map<Long, Long> finishMap = learningRecordList.stream().map(LearningRecord::getLessonId).collect(Collectors.groupingBy(r -> r, Collectors.counting()));

        //2.2 整理数据
        for (LearningPlanVO lesson : learningPlanVOList) {
            lesson.setCourseName(courseMap.get(lesson.getCourseId()).getName());//课程名称
            lesson.setSections(courseMap.get(lesson.getCourseId()).getSectionNum());//课程总小节数
            Integer weekLearnedSections = Math.toIntExact(finishMap.getOrDefault(lesson.getId(), 0L));
            lesson.setWeekLearnedSections(weekLearnedSections);
        }

        //3. 封装数据并返回
        learningPlanPageVO.setWeekPoints(0); //设置本周学习积分
        learningPlanPageVO.setWeekFinished(weekFinished);//设置本周已经学完小节数量
        learningPlanPageVO.setWeekTotalPlan(weekTotalPlan);//设置本周计划学习小节数量
        learningPlanPageVO.setList(learningPlanVOList);//设置计划中的课程列表
        return learningPlanPageVO;
    }
}